//  Based on code from the AS3 Eval Project by Metal Hurlant
//  http://eval.hurlant.com/

package 
{
	import flash.display.*;
	import flash.events.Event;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	import mx.controls.SWFLoader;
	
	public class ByteLoader2 
	{
		public var rootReference:DisplayObjectContainer = null;
		public var scopeReference:Object = null;
		public var l:SWFLoader = new SWFLoader;
		
		private var swf_start:Array = 
		[
			0x46, 0x57, 0x53, 0x09,                                 // FWS, Version 9
			0xff, 0xff, 0xff, 0xff,                                 // File length
			0x78, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x0b, 0xb8, 0x00,    // size [Rect 0 0 8000 6000] 
			0x00, 0x0c, 0x01, 0x00,                                 // 16bit le frame rate 12, 16bit be frame count 1 
			0x44, 0x11,                                                // Tag type=69 (FileAttributes), length=4  
			0x08, 0x00, 0x00, 0x00									//useNetwork 0, AS3 1, hasMetadata 0, useGPU 0, useDirectBlit 0
		];

		private var abc_header:Array = 
		[
			0x3f, 0x12,                                                // Tag type=72 (DoABC), length=next.
			//0xff, 0xff, 0xff, 0xff                                 // ABC length, not included in the copy. 
		];
		
		private var swf_end:Array =
		// the commented out code tells the player to instance a class "test" as a Sprite.
		//[/*0x09, 0x13, 0x01, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00, */ 0x40, 0x00]; // Tag type=1 (ShowFrame), length=0
		[0x09, 0x13, 0x01, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00, 0x40, 0x00]; // Tag type=1 (ShowFrame), length=0
		
		private var swf_end_global:Array =
		// the commented out code tells the player to instance a class "test" as a Sprite.
		[/*0x09, 0x13, 0x01, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00, */ 0x40, 0x00]; // Tag type=1 (ShowFrame), length=0
		//[0x09, 0x13, 0x01, 0x00, 0x00, 0x00, 0x74, 0x65, 0x73, 0x74, 0x00, 0x40, 0x00]; // Tag type=1 (ShowFrame), length=0
		
		
		function ByteLoader2() 
		{ 
		}
		
		/**
		 * Wraps the ABC bytecode inside the simplest possible SWF file, for
		 * the purpose of allowing the player VM to load it.
		 *  
		 * @param bytes: an ABC file
		 * @return a SWF file 
		 * 
		 */
		public function wrapInSWF(bytes:Array):ByteArray 
		{
			// wrap our ABC bytecodes in a SWF.
			var out:ByteArray = new ByteArray;
			out.endian = Endian.LITTLE_ENDIAN;
			
			for (var i:int=0;i<swf_start.length;i++) 
			{
				out.writeByte(swf_start[i]);
			}
			
			for (i=0;i<bytes.length;i++) 
			{
				var abc:ByteArray = bytes[i];
				
				for (var j:int=0;j<abc_header.length;j++) 
				{
					out.writeByte(abc_header[j]);
				}
				
				// set ABC length
				out.writeInt(abc.length);
				out.writeBytes(abc, 0, abc.length);
			}
			
			var swf_end_bytes:Array = ((scopeReference != null) ? swf_end : swf_end_global);
			
			for (i=0;i<swf_end_bytes.length;i++) 
			{
				out.writeByte(swf_end_bytes[i]);
			}
            
			// set SWF length
			out.position = 4;
			out.writeInt(out.length);
            
			// reset
			out.position = 0;
			return out;
		}
		
		/**
		 * Load the bytecodes passed into the flash VM, using
		 * the current application domain, or a child of it
		 *
		 * This probably always returns true, even when things fail horribly,
		 * due to the Loader logic waiting to parse the bytecodes until the 
		 * current script has finished running. 
		 * 
		 */
		public function loadBytes(bytes:*, inplace:Boolean=false):Boolean 
		{
			if (bytes is Array || (getType(bytes) == 2)) 
			{
				if (!(bytes is Array)) 
				{
					bytes = [ bytes ];
				}
				
				bytes = wrapInSWF(bytes);
			}
			
			try 
			{
				var c:LoaderContext = null;
				
				if (inplace) 
				{
					c = new LoaderContext(false, ApplicationDomain.currentDomain, null);
				}
				
				if(scopeReference != null)
					l.addEventListener(Event.COMPLETE, bytesLoaded);
					
				l.loaderContext = c;
				//l.loadBytes(bytes, c);
				l.load(bytes);
				
				return true;
			} 
			catch (e:*) 
			{
				trace(e);
			} 
			finally 
			{
				//trace("done.");
				//darn it. the running of the bytes doesn't happen until current scripts are done. no try/catch can work
			}
			
			return false;
		}
		
		public function bytesLoaded(evt:Event):void
		{
			//trace(":: BYTECODE LOADED ::");
			l.content["setScope"](scopeReference);
			l.content["setByteLoader"](this);
			l.removeEventListener(Event.COMPLETE, bytesLoaded);
			rootReference.addChild(l);
		}
		
		public function removeSWFLoader():void
		{
			rootReference.removeChild(l);
			//l.load(null);
			//l = null;
		}
		
		public function isSWF(data:ByteArray):Boolean 
		{
			var type:int = getType(data);
			return (type & 1) == 1;
		}
		
		/**
		 * getType.
		 * ripped from abcdump.as.
		 * 
		 * This looks at the array header and decides what it is.
		 * 
		 * (getType&1)==1 => it's  SWF
		 * (getType&2)==2 => it's  ABC
		 * (getType&4)==4)=> it's compressed
		 *  
		 * @param data
		 * @return 
		 * 
		 */
		public function getType(data:ByteArray):int 
		{
			data.endian = "littleEndian";
			var version:uint = data.readUnsignedInt();
			
			switch (version) 
			{
				case 46<<16|14:
				case 46<<16|15:
				case 46<<16|16:
					return 2;
				case 67|87<<8|83<<16|9<<24: // SWC9
				case 67|87<<8|83<<16|8<<24: // SWC8
				case 67|87<<8|83<<16|7<<24: // SWC7
				case 67|87<<8|83<<16|6<<24: // SWC6
					return 5;
				case 70|87<<8|83<<16|9<<24: // SWC9
				case 70|87<<8|83<<16|8<<24: // SWC8
				case 70|87<<8|83<<16|7<<24: // SWC7
				case 70|87<<8|83<<16|6<<24: // SWC6
				case 70|87<<8|83<<16|5<<24: // SWC5
				case 70|87<<8|83<<16|4<<24: // SWC4
					return 1;
				default:
					return 0;
			}
		}
	}
}